package com.instant.searcher;

import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Parser {
    //添加文档路径
    //如果使用\需要进行转义“\\”
    private static final String INPUT_PATH = "D:/beginning/homework/project/jdk-8u411-docs-all/docs/api";
    private Index index = new Index();

    //设置为public方便其他程序调用，private保证数据安全性
    //1.根据指定的路径枚举出路径中所有的子文件。
    //2.针对上面文件的路径，打开文件，读取文件内容，进行解析构建索引
    //3.把在内存中构建好的索引数据结构保存到指定的文件当中去。
    private void run() {
        long start = System.currentTimeMillis();
        ArrayList<File> fileList = new ArrayList<File>();
        enumFile(INPUT_PATH , fileList);

        //对文件进行遍历，提取搜索关键词
        for (File file: fileList){
            System.out.println("开始解析："+file.getAbsolutePath());
            //通过这个方法解析单个的HTML文件
            parseHTML(file);
        }
        //将构造好的数据结构进行保存
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("索引制作耗时"+(end-start)+"ms");
    }
    public void runByThread(){
        System.out.println("索引制作开始。");
        long start = System.currentTimeMillis();
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH , files);
        //多线程完成循环遍历,此处为了能够通过多线程制作索引，就直接引入线程池
        CountDownLatch latch = new CountDownLatch(files.size());
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(File file: files){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("解析"+file.getAbsolutePath());
                    parseHTML(file);
                    latch.countDown();
                }
            });
        }
        try {
            latch.await(60 , TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        executorService.shutdown();
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕"+(end-start)+"ms");
    }

    //第一个参数是从哪个目录进行递归遍历
    //第二个参数保存递归遍历的结果
    private void enumFile(String path , ArrayList<File> fileList){
        File pathFile = new File(path);
        //listFiles能够获取到pathFile 此文件下所有的目录与文件
        File[] files =  pathFile.listFiles();
        if (files != null) {
            for(File file: files){
                if(file.isFile()){
                    //判断文件结尾的字符endsWith，判断扩展名
                    if(file.getAbsolutePath().endsWith(".html")) {
                        fileList.add(file);
                    }
                }if(file.isDirectory()){
                    enumFile(file.getAbsolutePath() , fileList);
                }
            }
        }else{
            System.out.println("空文件！");
        }
    }

    private void parseHTML(File file) {
        //一条搜索结果：标题 ， 描述 ， url
        //1.解析标题
        String title = parseTitle(file);
        //2.解析出url
        String url = parseUrl(file);
        //3.解析出正文
        //String content = parseContent(file);
        String content = parseContentByRegex(file);
        //将解析后的内容添加到Index的数组当中去。
        index.addDoc(title , url , content);
    }

    private String parseTitle(File file) {
        return file.getName().substring(0,file.getName().lastIndexOf("."));
        //substring截取字符串，也可以使用f.getName().length() - ".html".length();
    }

    private String parseUrl(File file) {
        String part1 = file.getAbsolutePath().substring(INPUT_PATH.length());//后面的部分
        String part2 = "https://docs.oracle.com/javase/8/docs/api/";//前缀
        String url = part2 + part1;//此时的url中既包含正斜杠也包含反斜杠
        //替换也可以，不替换也可以，浏览器都可以识别。
        return url;
    }

    public String parseContent(File file) {
        //按字符读取文件。按字符而不是按字节。
        //java标准库中既提供了字符读取类 FileReader，也提供了字节读取类  FileInputStream
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file),1024 * 1024)){
            //拷贝开关
            boolean isCopy = true;
            //保存读取结果的容器
            StringBuilder content = new StringBuilder();
            while(true){
                int ret = bufferedReader.read();//此处的read返回值为int表示一些非法情况，读到末尾没有数据返回-1
                if(ret == -1){
                    //文件读完了
                    break;
                }
                //如果ret不是-1，此时的返回结果就是正常的字符了。
                char c = (char) ret;
                if(isCopy){
                    //开关是打开的状态，普通字符就应该拷贝到stringBuilder中
                    if(c == '<'){
                        isCopy = false;
                        continue;
                    }
                    //文档中的换行太多，不美观，可以换成空格
                    if(c == '\n'||c == '\r'){
                        c = ' ';
                    }
                    content.append(c);
                }else{
                    if(c == '>'){
                        isCopy = true;
                    }
                }
            }
            //fileReader.close();
            return content.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String readFile(File file){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(file) , 1024 * 1024)){
            StringBuilder content = new StringBuilder();
            while(true){
                int ret = bufferedReader.read();
                if(ret == -1){
                    break;
                }
                char c = (char) ret;
                if(c == '\n'||c == '\r'){
                    c = ' ';
                }
                content.append(c);
            }
            return content.toString();
        }catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }

    //正则表达式解析内容
    public String parseContentByRegex(File file) {
        //1.先把整个文件读到String里面
        String content = readFile(file);
        //2.替换掉script标签
        content = content.replaceAll("<script.*?>(.*?)</script>", " ");
        //3.不要忘记其他普通Html标签
        content = content.replaceAll("<.*?>", " ");
        //4.替换空格符,多个替换为一个
        //+需要替换的字符必须出现一次及以上才能够替换，*则是不需要出现此内容就可以进行替换
        content = content.replaceAll("\\s+" , " ");
        return content;
        //第三步和第二步顺序不能调换，如果调换第三步会直接用空格替换两个script标签，此时再进行第二部没有意义了，标签中的内容就无法替换了。

    }

    //对执行方法进行封装，应用启动之后可以由main方法决定执行。
    public static void main(String[] args) {
        //实现制作索引的过程
        Parser parser = new Parser();
        parser.runByThread();
    }
}
